
import message from './message';

/**
 * @type {WebSocket}
 * websocket 实例
 */
var socket = null;

/**
 * Object
 * WebSocket 处理的函数.
 * key:   string, 指 WebSocket 消息的 type
 * value: function, 调用的函数. 调用时, 参数为 WebSocket 消息内容, this = aurora.monitor
 */
var handlers = {
  "/show/message" : [handleCommand],
  "/asset/ticked" : [handleTicked],
  "/asset/updated" : [],
  "/asset/notExist" : [],
};

/**
 * 如果 socket 没有启动就发送信息, 可能会失败.
 * 这时, 把信息先缓存起来, 连接到服务器后再发出去.
 */
let queue = [];

let socketOpenListener = () => {
  console.log("连接状态监视服务器成功");
  if (queue.length > 0) {
    var q = queue;
    queue = [];

    q.forEach((data) => send(data));
  }
}

function setSocketOpenListener(l) {
  socketOpenListener = l;
}

/**
 * @param {string} type 
 * @param obj  function | object
 */
function putMessageHandler(type, obj) {
  switch (typeof(obj)) {
    case 'function': break;
    case 'object':
      if (!obj.callback) {
        throw obj;
      } break;
    default:
      throw obj;
  }

  let array = handlers[type];
  if (!array) {
    handlers[type] = array = [];
  }
  array.push(obj);
}

/**
 * @param {string} type 
 * @param obj  function | string
 *   如果当初调用 putMessageHandler 时, obj 参数是 object 类型的, 需要指定 obj.name, 按照 obj.name 来移除回调;
 *   否则直接函数判定相等来移除回调
 */
function removeMessageHandler(type, obj) {
  let array = handlers[type];
  if (!array) {
    return;
  }

  switch (typeof(obj)) {
    case 'function':
      array = array.filter((item) => {
        return !(typeof(item) == 'function' && item === obj);
      });
      break;
    case 'object':
      array = array.filter((item) => {
        return !(typeof(item) == 'object' && item.name === obj);
      });
  }

  if (array.length == 0) {
    delete handlers[type];
  } else {
    handlers[type] = array;
  }
}

/**
 * 创建连接
 * 这里暂时不考虑除了展示页 (displayType == 'display') 的情况
 * @param {string} sid 屏幕号
 */
function connect (sid) {
  var url = "ws://" + location.host + "/aurora/screen/" + sid;

  socket = new WebSocket(url);
  socket.onopen = function() {
    if (socketOpenListener) {
      socketOpenListener();
    }
  };
  socket.onmessage = function (event) {
    dispatchWebSocketMessage(JSON.parse(event.data));
  };
  socket.onclose = () => whenConnectClosed(sid);
  socket.onerror = function(e) { 
    console.error(e);
  };

  global.closeConnectOnPurpose = () => {
    socket.close();
  }
};

function whenConnectClosed(sid) {
  console.log("与远程状态监视服务器的连接关闭");
  message.release('connect-close', null);

  // 第一次 5 秒后重连, 第二次在第一次失败后 10 秒重连, 第三次 15 秒...
  reconnect(sid, 1);
}

/**
 * 重连流程, 可能需要一段时间. 流程是, 先发请求 reconnect/{sid}, 如果后端允许了会 ws 重连
 * @param {string} sid 屏幕号 
 */
function reconnect(sid, times) {
  if (times > 10) {
    message.release('reconnect-fail', { times, reconnectNext : false });
    return Promise.reject();
  }

  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log(`第 ${times} 次重连...`);
      message.release('reconnect-begin', { times });
      resolve();
    }, 5000 * times);
  }).then(() => fetch(`/aurora/reconnect/${sid}`).then(d => d.json()).then(d => {
    if (d.result == true) {
      return Promise.resolve();
    } else {
      console.log(`服务器拒绝重连`, d);
      message.release('reconnect-fail', { times, reconnectNext : false });
      return Promise.reject(false); // 应该阻止调用链, 直接结束
    }
    // 超时返回的是连接错误, reject(Error)
  })).then(() => {
    connect(sid);
  }).catch((result) => {
    if (result == false) {
      // 调用链结束
      return;
    } else {
      // 继续下一次的重连
      message.release('reconnect-fail', { times, reconnectNext : true });
      return reconnect(sid, times + 1);
    }
  })
}

// ****** ws 消息处理的函数, 接收部分

function dispatchWebSocketMessage (msg) {
  var type = msg.type;
  var data = msg.data;
  /** @type {Array | null} */
  let callbacks = handlers[type];

  if (callbacks && callbacks.length) {
    callbacks.forEach(callback => {
      try {
        switch (typeof(callback)) {
          case 'function':
            {
              callback.call(global, data);
            } break;
          case 'object':
            {
              callback.callback.call(callback.scene | callback.context | global, data);
            } break;
        }
      } catch (e) {
        console.error(e);
      }
    });
  } else {
    console.log("从服务器那里收到消息: ", msg);
  }
};

function handleCommand (data) {
  message.release('screen-command', data);
};

/**
 * 资源数据变化往这里发 - B 站弹幕数据就往这里了
 */
function handleTicked (data) {
  message.release('screen-ticked', data);
};


// ****** ws 发送消息部分

/**
 * 发送展示屏状态. 一般发送的是播放状态
 */
function sendStatus (status) {
  let d = {
    type : '/status',
    data : status
  };
  send(d);
}

/**
 * 发送绑定资源消息
 * @param {string} assetId 资源的 uuid
 * @param {object} description 附带的数据. 可以 null
 */
function sendAttachAssetEvent (assetId, description) {
  send({
    type : '/asset/attach',
    data : { assetId, description }
  });
}

/**
 * 发送解绑资源消息
 * @param {string} assetId 资源的 uuid
 */
function sendDetachAssetEvent (assetId) {
  send({
    type : '/asset/detach',
    data : { assetId }
  });
}

function send (obj) {
  if (socket == null || socket.readyState != socket.OPEN) {
    console.log("尝试发送状态数据失败, 失败数据存队列", obj);
    queue.push(obj);
    return;
  }

  try {
    socket.send(JSON.stringify(obj));
  } catch (e) {
    console.error(e);
  }
}

export default {
  setSocketOpenListener,
  putMessageHandler, removeMessageHandler,
  connect, sendStatus, sendAttachAssetEvent, sendDetachAssetEvent
};
